import os
import json
from ..utils.utils import *
from .dbcyacc import Parser

__all__ = ['DBC', 'DbcSignal', 'DbcMessage']
__version__ = '1.0.0'

class DbcInternal:
    def __init__(self, dbc, self_node):
        self._dbc = dbc
        self._self_node = self_node
        self._messages = {}
        self._signals = []
        self._ecus = []
        self._attr = []
        self._valtable = []
        self._cm = []

        self.parse()
    def messages(self) -> dict: return self._messages
    def signals(self) -> list: return self._signals
    def parse(self):
        for msg_id, msg in self._dbc['boList'].items():
            self._messages[msg_id] = {}
            self._messages[msg_id]['Name'] = msg['name']
            self._messages[msg_id]['IDFormat'] = 'Standard'
            self._messages[msg_id]['Dlc'] = msg['length']
            if(msg['node'] == self._self_node):
                self._messages[msg_id].update({'Direction' : 'Tx'})
            else:
                self._messages[msg_id].update({'Direction' : 'Rx'})
            self._messages[msg_id]['Comment'] = ''
            self._messages[msg_id]['Attribute'] = []
            self._messages[msg_id]['Signals'] = []

            sgList = []
            if 'sgList' in msg:
                for sg in msg['sgList']:
                    signal = {}
                    signal['ID'] = int(msg_id, 16)
                    signal['Name'] = sg['name']
                    signal['StartBit'] = sg['start']
                    signal['LengthBit'] = sg['size']
                    signal['ByteOrder'] = 'Intel' if sg['endian'] else 'Motorola'
                    signal['ValueType'] = 'Unsigned' if sg['sign'] == '+' else 'signed'
                    signal['InitialValue'] = 0
                    signal['Factor'] = sg['factor']
                    signal['Offset'] = sg['offset']
                    signal['Minimum'] = sg['min']
                    signal['Maximum'] = sg['max']
                    signal['Unit'] = sg['unit']
                    signal['ValueTable'] = {}
                    signal['Comment'] = ''
                    signal['Attribute'] = []

                    sgList.append(signal)
            self._messages[msg_id]['Signals'] = sgList
            self._signals.extend(sgList)

        for ba in self._dbc['baList']:
            if ba[1] == 'BO_':
                if hex(ba[2]) in self._messages:
                    self._messages[hex(ba[2])]['Attribute'].append({ba[0] : ba[3]})

            if ba[1] == 'SG_':
                if hex(ba[2]) in self._messages:
                    for idx, sig in enumerate(self._messages[hex(ba[2])]['Signals']):
                        if ba[3] == sig['Name']:
                            self._messages[hex(ba[2])]['Signals'][idx]['Attribute'].append({ba[0] : ba[4]})
                            break
            else: pass
        
        for cm in self._dbc['cmList']:
            if cm[0] == 'BO_':
                if hex(cm[1]) in self._messages:
                    self._messages[hex(cm[1])]['Comment'] = cm[2]
            if cm[0] == 'SG_':
                if hex(cm[1]) in self._messages:
                    for idx, sig in enumerate(self._messages[hex(cm[1])]['Signals']):
                        if cm[2] == sig['Name']:
                            self._messages[hex(cm[1])]['Signals'][idx]['Comment'] = cm[3]
                            break
        
        for val in self._dbc['valList']:
            if hex(val['id']) in self._messages:
                for idx, sig in enumerate(self._messages[hex(val['id'])]['Signals']):
                    if val['signame'] == sig['Name']:
                        for k, v in val['vt'].items():
                            self._messages[hex(val['id'])]['Signals'][idx]['ValueTable'][k] = v


        dbc_out = os.path.join("output", "components",'dbc', 'dbc_msg_list.json')
        os.makedirs(os.path.dirname(dbc_out), exist_ok = True)
        with open(dbc_out, 'w', encoding = 'utf-8') as f:
            f.write(json.dumps(self._messages, indent = 4, ensure_ascii = False))


class DbcSignal:
    def __init__(self, sig):
        self.msgid = sig['ID']
        self.name = sig['Name']
        self.start_bit = sig['StartBit']
        self.length_bit = sig['LengthBit']
        self.byte_order = sig['ByteOrder']
        self.value_type = sig['ValueType']
        self.initial_value = sig['InitialValue']
        self.factor = sig['Factor']
        self.offset = sig['Offset']
        self.min = sig['Minimum']
        self.max = sig['Maximum']
        self.unit = sig['Unit']
        self.value_table = sig['ValueTable']
        self.comment = sig['Comment']
        self.attribute = sig['Attribute']

    def __str__(self) -> str:
        return f'name:{self.name}, start bit:{self.start_bit}, size:{self.length_bit}, comment:{self.comment}'


class DbcSignals:
    def __init__(self, sigs : list):
        self._sigs = sigs
        self._index = 0

    def __iter__(self):
        self._index = 0
        return self

    def __next__(self):
        if self._index == len(self._sigs):
            raise StopIteration
        sig = self._sigs[self._index]
        self._index += 1
        return DbcSignal(sig)

    def __getitem__(self, value):
        if isinstance(value, int):
            return DbcSignal(self._sigs[value])
        elif isinstance(value, str):
            sig = [sig for sig in self._sigs if sig['Name'] == value]
            return DbcSignal(sig[0]) if sig else None
        else: return None

    def __len__(self):
        return len(self._sigs)

    def __contains__(self, value):
        if isinstance(value, str):
            return True if [sig for sig in self._sigs if sig['Name'] == value] else False
        else: return False


class DbcMessage:
    def __init__(self, msg_id : int, msg : dict):
        self._msg = msg

        self.msg_id = msg_id
        self.name = self._msg['Name']
        self.idformat = self._msg['IDFormat']
        self.length = self._msg['Dlc']
        self.direction = self._msg['Direction']
        self.comment = self._msg['Comment']
        self.attribute = self._msg['Attribute']

        self._sigs = self._msg['Signals']

        self.signals = DbcSignals(self._sigs)

    def __str__(self) -> str:
        return f'name:{self.name}, msgid:{self.msg_id}, size:{self.length}, comment:{self.comment}'


class DbcMessages:
    def __init__(self, msgs : dict):
        self._msgs = msgs
        self._msg_keys = list(self._msgs.keys())
        self._index = 0

    def __iter__(self):
        self._index = 0
        return self

    def __next__(self) -> DbcMessage:
        if self._index == len(self._msg_keys):
            raise StopIteration
        msg_id = self._msg_keys[self._index]
        self._index += 1
        return DbcMessage(int(msg_id, 16), self._msgs[msg_id])

    def __getitem__(self, key):
        if isinstance(key, int):
            try:
                return DbcMessage(key, self._msgs[hex(key)])
            except KeyError:
                return None
        elif isinstance(key, str):
            idlist = [id for id, msg in self._msgs.items() if msg['Name'] == key]
            return DbcMessage(int(idlist[0], 16), self._msgs[idlist[0]]) if idlist else None
        else: return None

    def __len__(self):
        return len(self._msg_keys)

    def __contains__(self, value):
        try:
            if isinstance(value, int):
                return True if hex(value) in self._msgs else False
            elif isinstance(value, str):
                return True if [id for id, msg in self._msgs.items() if msg['Name'] == value] else False
        except KeyError:
            return False


class DBC:
    '''dbc parse'''
    def __init__(self, path, self_node):
        self._self_node = self_node
        self._path = path

        with open(path, 'r', encoding='gbk') as fp:
            data = fp.read()

        parse = Parser()
        self._dbc = parse.parse(data)
        internal = DbcInternal(self._dbc, self._self_node)
        self._messages = internal.messages()
        self._signals = internal.signals()

        dbc_out = os.path.join("output", "components",'dbc', 'dbc.json')
        os.makedirs(os.path.dirname(dbc_out), exist_ok = True)
        with open(dbc_out, 'w', encoding = 'utf-8') as f:
            f.write(json.dumps(self._dbc, indent = 4, ensure_ascii = False))

        self.msgs = DbcMessages(self._messages)
        self.sigs = DbcSignals(self._signals)

